#include "spk_camera.h"
#include "config.h"
#include "video.h"
#include "video_v4l2.h"

#define LOG_TAG "video_v4l2"
#define LOG_LVL LOG_LVL_INFO
#include "spk_ulog.h"

#include <stdlib.h> // init_mman --> calloc
#include <sys/stat.h>
#include <sys/mman.h>
#include <errno.h>
#include <fcntl.h> // open / close
#include <linux/videodev2.h>

#define VIDEO_DEVICE "/dev/video0"
#define FORCE_FORMAT 1

// 使用匿名命名空间，和 static 全局静态函数等价
namespace
{
    // re do the ioctl until done
    int xioctl(int fh, int request, void *arg)
    {
        int r;
        do
        {
            r = ioctl(fh, request, arg);
        } while (-1 == r && EINTR == errno);

        return r;
    }
}

cls_video_v4l2::cls_video_v4l2(cls_video *p_video)
{
    video = p_video;

    open_device();
    init_device();
    start_capturing();
}

cls_video_v4l2::~cls_video_v4l2()
{
    video = nullptr;

    stop_capturing();
    uninit_device();
    close_device();
}

void cls_video_v4l2::open_device()
{
    struct stat st;
    if (stat(VIDEO_DEVICE, &st) == -1)
    {
        fprintf(stderr, "Cannot identify '%s': %d, %s\n",
                VIDEO_DEVICE, errno, strerror(errno));
        return;
    }

    if (!S_ISCHR(st.st_mode))
    {
        fprintf(stderr, "%s is not device", VIDEO_DEVICE);
        return;
    }

    int fd = open(VIDEO_DEVICE, O_RDWR /* required */ | O_NONBLOCK, 0);
    if (fd == -1)
    {
        fprintf(stderr, "Cannot open '%s': %d, %s\\n",
                VIDEO_DEVICE, errno, strerror(errno));
        return;
    }
    camera_fd = fd;
}
void cls_video_v4l2::close_device()
{
    close(camera_fd);
}

void cls_video_v4l2::init_device()
{
    struct v4l2_capability cap;  // 存储设备的能力信息
    // struct v4l2_cropcap cropcap; // 存储设备裁剪能力  no cripcap
    // struct v4l2_crop crop;       // 存储裁剪参数
    struct v4l2_format fmt;      // 存储视频格式参数

    unsigned int min; // 用于计算缓冲区大小的辅助变量

    // 查询设备能力
    if (xioctl(camera_fd, VIDIOC_QUERYCAP, &cap) == -1)
    {
        fprintf(stderr, "get VIDIOC_QUERYCAP error: %d, %s\n", errno, strerror(errno));
        return;
    }

    // 检查设备是否为视频捕获设备
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
        fprintf(stderr, "%s is not video capture device\n",
                VIDEO_DEVICE);
        return;
    }

    // // 查询裁剪能力并设置默认裁剪参数
    // /* Select video input, video standard and tune here. */
    // memset(&cropcap, 0, sizeof(cropcap));
    // cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    // if (xioctl(camera_fd, VIDIOC_CROPCAP, &cropcap) == 0)
    // {
    //     memset(&crop, 0, sizeof(crop));
    //     crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    //     crop.c = cropcap.defrect; /* reset to default */

    //     // MARK: error occurs
    //     if (xioctl(camera_fd, VIDIOC_S_CROP, &crop) == -1)
    //     {
    //         fprintf(stderr, "set VIDIOC_S_CROP failed: %d, %s\n", errno, strerror(errno));
    //     }
    // }
    // else
    // {
    //     fprintf(stderr, "get VIDIOC_CROPCAP failed: %d, %s\n", errno, strerror(errno));
    // }

    // 枚举设备支持的像素格式并打印
    /* Enum pixel format */
    for (int i = 0; i < 20; i++)
    {
        struct v4l2_fmtdesc fmtdesc;
        fmtdesc.index = i;
        fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (xioctl(camera_fd, VIDIOC_ENUM_FMT, &fmtdesc) == -1)
            break;

        printf("Format index %d: FourCC: %.4s, Description: %s\n", i, (char *)&fmtdesc.pixelformat, fmtdesc.description);
    }

    // 枚举设备支持的帧尺寸并打印
    // check the width and height
    struct v4l2_frmsizeenum fs;
    memset(&fs, 0, sizeof(fs));
    fs.pixel_format = V4L2_PIX_FMT_YUYV; // 前面可以看到相机支持的像素格式可能不只有YUV，但是这里枂只罗列YUV支持的格式
    fs.index = 0;
    while (ioctl(camera_fd, VIDIOC_ENUM_FRAMESIZES, &fs) == 0)
    {
        // if(xioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fs) == 0)
        // {
        //     break;
        // }
        if (fs.type == V4L2_FRMSIZE_TYPE_DISCRETE)
        {
            printf("Discrete frame size: width=%d, height=%d\n", fs.discrete.width, fs.discrete.height);
        }
        fs.index++;
    }

    // 设置或获取视频格式参数
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (FORCE_FORMAT) // 如果为真，则设置格式，如果为假，则获取默认格式
    {

        fmt.fmt.pix.width = video->cfg_cur->video_iw;
        fmt.fmt.pix.height = video->cfg_cur->video_ih;

        fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
        fmt.fmt.pix.field = V4L2_FIELD_ANY;

        if (xioctl(camera_fd, VIDIOC_S_FMT, &fmt) == -1)
        {
            fprintf(stderr, "get VIDIOC_S_FMT failed: %d, %s\n", errno, strerror(errno));
            return;
        }

        /* Note VIDIOC_S_FMT may change width and height. */
    }
    else
    {
        /* Preserve original settings as set by v4l2-ctl for example */
        if (xioctl(camera_fd, VIDIOC_G_FMT, &fmt) == -1)
        {
            fprintf(stderr, "get VIDIOC_G_FMT failed: %d, %s\n", errno, strerror(errno));
            return;
        }
    }

    // 设置流参数（帧率）
    // stream parameters
    struct v4l2_streamparm stream_parm;
    memset(&stream_parm, 0, sizeof(stream_parm));
    stream_parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    stream_parm.parm.capture.timeperframe.numerator = 1;
    // stream_parm.parm.capture.timeperframe.denominator = 60;
    stream_parm.parm.capture.timeperframe.denominator = 30;
    // stream_parm.parm.capture.timeperframe.denominator = 25;

    // 先set看看
    if (ioctl(camera_fd, VIDIOC_S_PARM, &stream_parm) == -1)
    {
        fprintf(stderr, "set VIDIOC_S_PARM failed: %d, %s\n", errno, strerror(errno));
        return;
    }
    // 然后get之后打印，看是否set成功
    if (ioctl(camera_fd, VIDIOC_G_PARM, &stream_parm) == -1)
    {
        fprintf(stderr, "set VIDIOC_G_PARM failed: %d, %s\n", errno, strerror(errno));
        return;
    }
    LOG_I("\nv4l2 time_numerator: %d\nv4l2 time_denominator: %d\n", stream_parm.parm.capture.timeperframe.numerator, stream_parm.parm.capture.timeperframe.denominator);

    /* Buggy driver paranoia. */
    min = fmt.fmt.pix.width * 2;
    if (fmt.fmt.pix.bytesperline < min)
        fmt.fmt.pix.bytesperline = min;
    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
    if (fmt.fmt.pix.sizeimage < min)
        fmt.fmt.pix.sizeimage = min;

    init_mmap();
}
void cls_video_v4l2::uninit_device()
{
    unsigned int i;

    for (i = 0; i < n_buffers; ++i)
        munmap(buffers[i].start, buffers[i].length);

    free(buffers);
}

void cls_video_v4l2::start_capturing()
{
    unsigned int i;
    enum v4l2_buf_type type;

    for (i = 0; i < n_buffers; ++i)
    {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if (xioctl(camera_fd, VIDIOC_QBUF, &buf) == -1)
        {
            fprintf(stderr, "set VIDIOC_QBUF failed: %d, %s\n", errno, strerror(errno));
            return;
        }
    }
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (xioctl(camera_fd, VIDIOC_STREAMON, &type) == -1)
    {
        fprintf(stderr, "set VIDIOC_STREAMON failed: %d, %s\n", errno, strerror(errno));
        return;
    }
}

void cls_video_v4l2::stop_capturing()
{
    enum v4l2_buf_type type;

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    xioctl(camera_fd, VIDIOC_STREAMOFF, &type);
}

int cls_video_v4l2::read_frame(ctx_image_data *img_data)
{
    int r;
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(camera_fd, &fds);

    struct timeval tv;
    tv.tv_sec = 2; // 2s 超时
    tv.tv_usec = 0;

    r = select(camera_fd + 1, &fds, NULL, NULL, &tv);

    if (r > 0)
    {
        struct v4l2_buffer buf;

        memset(&buf, 0, sizeof(buf));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;

        if (xioctl(camera_fd, VIDIOC_DQBUF, &buf) == -1)
        {
            if (errno == EAGAIN || errno == EINTR)
            {
                return 0;
            }
            else
            {
                fprintf(stderr, "set VIDIOC_DQBUF failed: %d, %s\n", errno, strerror(errno));
                return -1;
            }
        }

        LOG_D("%d   %d\n", buf.index, n_buffers);
        if (buf.index < n_buffers)
        {
            // codec image, put processed data into buffer
            if (buf.bytesused == img_data->size)
            {
                memcpy(img_data->data, buffers[buf.index].start, img_data->size);
            }
            else
            {
                LOG_E("image_data size(%d) != real size(%d)", img_data->data, buf.bytesused);
                return -1;
            }
            if (xioctl(camera_fd, VIDIOC_QBUF, &buf) == -1)
            {
                fprintf(stderr, "set VIDIOC_QBUF failed: %d, %s\n", errno, strerror(errno));
                return -1;
            }
        }
    }
    else if (r == 0)
    {
        fprintf(stderr, "select timeout\n");
    }
    else
    {
        if (EINTR == errno || EAGAIN == errno)
        {
            /* Nothing to do. */
        }
        else
        {
            fprintf(stderr, "select failed: %d, %s\n", errno, strerror(errno));
            return -1;
        }
    }

    return 0;
}

void cls_video_v4l2::init_mmap()
{
    struct v4l2_requestbuffers req;
    memset(&req, 0, sizeof(req));

    req.count = 16;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (xioctl(camera_fd, VIDIOC_REQBUFS, &req) == -1)
    {
        fprintf(stderr, "set VIDIOC_REQBUFS failed: %d, %s\n", errno, strerror(errno));
        return;
    }

    if (req.count < 2)
    {
        fprintf(stderr, "Insufficient buffer memory on %s\n",
                VIDEO_DEVICE);
        return;
    }

    buffers = (struct buffer *)calloc(req.count, sizeof(*buffers));

    if (!buffers)
    {
        fprintf(stderr, "Out of memory\n");
        return;
    }

    for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
    {
        struct v4l2_buffer buf;
        memset(&buf, 0, sizeof(buf));

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = n_buffers;

        if (xioctl(camera_fd, VIDIOC_QUERYBUF, &buf) == -1)
        {
            fprintf(stderr, "set VIDIOC_QUERYBUF %u failed: %d, %s\n", n_buffers, errno, strerror(errno));
            return;
        }

        buffers[n_buffers].length = buf.length;
        buffers[n_buffers].start =
            mmap(NULL /* start anywhere */,
                 buf.length,
                 PROT_READ | PROT_WRITE /* required */,
                 MAP_SHARED /* recommended */,
                 camera_fd, buf.m.offset);

        if (MAP_FAILED == buffers[n_buffers].start)
        {
            fprintf(stderr, "mmap %u failed: %d, %s\n", n_buffers, errno, strerror(errno));
            return;
        }
    }
}
